package me.zhengjie.config.filter;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import me.zhengjie.request.ATMRContext;
import me.zhengjie.utils.StringUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static me.zhengjie.request.ContextAssembler.assembleBusContext;

@WebFilter(urlPatterns = "/*")
@Component
public class OnceRequestGlobalFilter implements Filter {
    private static final String REQUEST_START_PROFILE_MESSAGE = "%s;请求开始时间：%s";

    private static final String REQUEST_END_PROFILE_MESSAGE = "%s;请求结束时间：%s，共耗时：%s";

    private final String CURRENT_CLZ_NAME = this.getClass().getSimpleName();

    private static final Logger log = LoggerFactory.getLogger(OnceRequestGlobalFilter.class);

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
        try {
            long startTime = System.currentTimeMillis();

            prepareFilter(request);
            /*boolean excludeUrl = isExcludeUrl(request);
            if (!excludeUrl) {
                logCaptureRequestBeforeFilter(request);
            }*/

            filterChain.doFilter(request, response);

            //logCaptureRequestAfterFilter(request, response, startTime, excludeUrl);
        } catch (Exception e) {
            log.error("Filter doHandler exception", e);
        } finally {
            cleanupFilter(request);
        }
    }

    @Override
    public void destroy() {
        ATMRContext.clear();
    }


    /**
     * 准备执行过滤
     *
     * @param request
     */
    private void prepareFilter(ServletRequest request) {
        assembleBusContext(request);
    }

    /**
     * 清理过滤器
     *
     * @param request
     */
    private void cleanupFilter(ServletRequest request) {
        ATMRContext.clear();
    }

    /**
     * 是否是放行白名单
     *
     * @param request
     * @return
     */
    private boolean isExcludeUrl(ServletRequest request) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String servletPath = httpServletRequest.getServletPath();

        List<String> excludeUrls = Lists.newArrayList(".jsp", ".css", ".html", ".jpg", ".png", ".jpeg", ".mp3", ".mp4", ".mv", "/api/menus/build", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security", "swagger-resources", "springfox-swagger-ui");
        return excludeUrls.parallelStream().anyMatch(s -> servletPath.endsWith(s) || servletPath.contains(s) || servletPath.startsWith(s));
    }

    /**
     * 抓取请求中的ip、host等标准化参数进行记录，并打印到日志
     *
     * @param req
     */
    private void logCaptureRequestBeforeFilter(ServletRequest req) {
        HttpServletRequest request = (HttpServletRequest) req;
        String servletPath = request.getServletPath();
        //记录访问日志
        Map<String, String> extraInfoMap = new HashMap<>();
        extraInfoMap.put("Accessor IP", StringUtils.getIp(request));
        extraInfoMap.put("Host IP", request.getRemoteHost());
        extraInfoMap.put("UrlPath", request.getRequestURL().toString());
        extraInfoMap.put("QueryString", request.getQueryString());
        extraInfoMap.put("UserAgent", request.getHeader("User-Agent"));
        extraInfoMap.put("Cookie", request.getHeader("Cookie"));
        extraInfoMap.put("Referer", request.getHeader("Referer"));
        Enumeration<String> headerEnum = request.getHeaderNames();
        if (headerEnum != null) {
            Map<String, String> headerMap = new HashMap<>();
            while (headerEnum.hasMoreElements()) {
                String headerName = headerEnum.nextElement();
                if (headerName != null && (headerName.equalsIgnoreCase("User-Agent")
                        || headerName.equalsIgnoreCase("Cookie")
                        || headerName.equalsIgnoreCase("Referer"))) {
                    continue;
                }
                String value = request.getHeader(headerName);
                headerMap.put(headerName, value);
            }
            extraInfoMap.put("header", JSON.toJSONString(headerMap));
        }
        log.info(JSON.toJSONString(extraInfoMap));
        log.info(CURRENT_CLZ_NAME + String.format(REQUEST_START_PROFILE_MESSAGE,
                servletPath,
                DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")), extraInfoMap);
    }

    /**
     * @param req
     * @param startTime
     */
    @SneakyThrows
    private void logCaptureRequestAfterFilter(ServletRequest req, ServletResponse resp, long startTime, boolean excludeUrl) {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        String servletPath = request.getServletPath();
        String endTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        long cost = System.currentTimeMillis() - startTime;
        if (!excludeUrl) {
            log.info(CURRENT_CLZ_NAME + String.format(REQUEST_END_PROFILE_MESSAGE,
                    servletPath,
                    endTime,
                    cost + "ms"));
        }
    }


    private String printRequestLog(final HttpServletRequest request) {
        StringBuilder msg = new StringBuilder();
        msg.append("Request请求：");
        HttpSession session = request.getSession(false);
        if (session != null) {
            msg.append("sessionId = ").append(session.getId()).append("; ");
        }
        if (request.getMethod() != null) {
            msg.append("method = ").append(request.getMethod()).append("; ");
        }
        if (request.getContentType() != null) {
            msg.append("contentType = ").append(request.getContentType()).append("; ");
        }
        msg.append("uri = ").append(request.getRequestURI());
        if (request.getQueryString() != null) {
            msg.append('?').append(request.getQueryString());
        }
        return msg.toString();
    }

    private boolean isBinaryContent(final HttpServletRequest request) {
        if (request.getContentType() == null) {
            return false;
        }
        return request.getContentType().startsWith("image")
                || request.getContentType().startsWith("video")
                || request.getContentType().startsWith("audio");
    }

    private boolean isMultipart(final HttpServletRequest request) {
        return request.getContentType() != null
                && request.getContentType().startsWith("multipart/form-data");
    }



    private List<String> getParam(final HttpServletRequest request,
                                  final HttpServletResponse response) {
        final String requestUri = request.getRequestURI();
        final String requestParams = getRequestParams(request);
        final String requestString = getRequestString(request);
        final String responseString = getResponseString(response);
        final int responseStatus = response.getStatus();

        final List<String> logs = Lists.newArrayList();
        logs.add("uri=" + requestUri);
        logs.add("status=" + responseStatus);
        logs.add("requestContentType=" + request.getContentType());
        logs.add("responseContentType=" + response.getContentType());
        logs.add("params=" + requestParams);
        logs.add("request=" + requestString);
        logs.add("response=" + responseString);

        log.info(String.join(",", logs));
        return logs;
    }

    private String getRequestParams(final HttpServletRequest request) {
        final Map<String, String[]> requestParams = Maps.newHashMap(request.getParameterMap());
        final List<String> pairs = Lists.newArrayList();
        if (MapUtils.isNotEmpty(requestParams)) {
            for (final Map.Entry<String, String[]> entry : requestParams.entrySet()) {
                final String name = entry.getKey();
                final String[] value = entry.getValue();
                if (value == null) {
                    pairs.add(name + "=");
                } else {
                    for (final String v : value) {
                        pairs.add(name + "=" + StringUtils.trimToEmpty(v));
                    }
                }
            }
        }
        String requestParamsStr = CollectionUtils.isEmpty(pairs) ? StringUtils.EMPTY : String.join("&", pairs);
        if (StringUtils.equalsIgnoreCase(request.getContentType(), MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            try {
                requestParamsStr = URLDecoder.decode(requestParamsStr, StandardCharsets.UTF_8.name());
            } catch (UnsupportedEncodingException ignored) {
            }
        }
        return requestParamsStr;
    }

    private String getRequestString(final HttpServletRequest request) {
        final ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            try {
                final byte[] buf = wrapper.getContentAsByteArray();
                return new String(buf, wrapper.getCharacterEncoding()).replaceAll("\n|\r", "");
            } catch (UnsupportedEncodingException e) {
                return "[UNKNOWN]";
            }
        }
        return StringUtils.EMPTY;
    }

    private String getResponseString(final HttpServletResponse response) {
        final ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                final byte[] buf = wrapper.getContentAsByteArray();
                return new String(buf, wrapper.getCharacterEncoding()).replaceAll("\n|\r", "");
            } catch (UnsupportedEncodingException e) {
                return "[UNKNOWN]";
            }
        }
        return StringUtils.EMPTY;
    }
}

